package com.dooioo.pns.base.service;

import com.dooioo.pns.base.dao.QueryDao;
import com.dooioo.pns.base.model.Pagination;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.session.Configuration;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Service 通用方法 User: Jail Hu Date: 2014-4-10 下午18:21
 */
public abstract class BaseService<T> {

	protected Class<T> entityClass;
	private static final String DAO_QUERY_FOR_PAGINATE = "queryForPaginate";
	private static final String DAO_COUNT = "count";
	@Autowired
		protected QueryDao queryDao;

	@SuppressWarnings("unchecked")
	public BaseService() {
		entityClass = getSuperClassGenricType(getClass(), 0);
	}

	/**
	 * 反馈Dao连接
	 *
	 * @return
	 */
	public Connection getConnection() {
		return this.queryDao.getSqlSession().getConnection();
	}

	/**
	 * 获得指定记录
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 *            查询参数(对象)
	 * @return T
	 */
	public T selectOne(String sqlId, Object obj) {
		return this.queryDao.getSqlSession().selectOne(sqlId(sqlId), obj);
	}

	public T selectOneById(String sqlId, Serializable id) {
		return this.queryDao.getSqlSession().selectOne(sqlId(sqlId), id);
	}

	/**
	 * 获得列表
	 *
	 * @param sqlId
	 *            xml的ID ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @return List<T>
	 */
	public List<T> selectList(String sqlId) {
		return this.queryDao.getSqlSession().selectList(sqlId(sqlId));
	}

	/**
	 * 获得列表
	 *
	 * @param sqlId
	 *            xml的ID ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 *            查询参数(对象)
	 * @return List<T>
	 */
	public List<T> selectList(String sqlId, Object obj) {
		return this.queryDao.getSqlSession().selectList(sqlId(sqlId), obj);
	}

	/**
	 * 获取结果的map 集合
	 *
	 * @param sqlId
	 *            xml的ID ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 *            查询参数(对象)
	 * @return Map<String,Object>
	 */
	public Map<String, Object> selectMap(String sqlId, Object obj, String key) {
		return this.queryDao.getSqlSession().selectMap(sqlId, obj, key);
	}

	/**
	 * 获取结果的map 集合
	 *
	 * @param sqlId
	 *            xml的ID ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 *            查询参数(对象)
	 * @return Map<String,Object>
	 */
	public Map<String, Map<String, String>> selectMap(String sqlId, String key) {
		return this.queryDao.getSqlSession().selectMap(sqlId, key);
	}

	public Map<Object, Object> selectMapTwo(String sqlId, Object obj, String key) {
		return this.queryDao.getSqlSession().selectMap(sqlId, obj, key);
	}

	/**
	 * 获得数据统计数
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @return
	 */
	public int count(String sqlId) {
		return this.queryDao.getSqlSession().selectOne(sqlId(sqlId));
	}

	/**
	 * 获得数据统计数
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 * @return
	 */
	public int count(String sqlId, Object obj) {
		return this.queryDao.getSqlSession().selectOne(sqlId(sqlId), obj);
	}

	/**
	 * 插入行数据
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @return
	 */
	public boolean insert(String sqlId) {
		return this.queryDao.getSqlSession().insert(sqlId(sqlId)) > 0;
	}

	/**
	 * 插入行数据
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 * @return
	 */
	public boolean insert(String sqlId, Object obj) {
		return this.queryDao.getSqlSession().insert(sqlId(sqlId), obj) > 0;
	}

	/**
	 * 插入行数据，并返回主键id
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 *            对象名称 mysql xml 返回主键写法 <selectKey
	 *            resultType="java.lang.Integer" order="AFTER" keyProperty="id"
	 *            > SELECT LAST_INSERT_ID() AS ID </selectKey>
	 * @return 主键id
	 */
	public int insertAndReturnId(String sqlId, Object obj) {
		return this.queryDao.getSqlSession().insert(sqlId(sqlId), obj);
	}

	/**
	 * 更新行数据
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @return
	 */
	public boolean update(String sqlId) {
		return this.queryDao.getSqlSession().update(sqlId(sqlId)) > 0;
	}

	/**
	 * 更新行数据
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 * @return
	 */
	public boolean update(String sqlId, Object obj) {
		return this.queryDao.getSqlSession().update(sqlId(sqlId), obj) > 0;
	}

	/**
	 * 删除行数据
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @return
	 */
	public boolean delete(String sqlId) {
		return this.queryDao.getSqlSession().delete(sqlId(sqlId)) > 0;
	}

	/**
	 * 删除行数据
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 * @return
	 */
	public boolean delete(String sqlId, Object obj) {
		return this.queryDao.getSqlSession().delete(sqlId(sqlId), obj) > 0;
	}

	/**
	 * 批量删除请到 xml 中使用 《foreach collection="list" item="item" index="index"
	 * separator="," 》 《/foreach》实现
	 *
	 * @return
	 */
	@Deprecated
	public void batchInsert() {
	}

	/**
	 * 生成ibatis的sqlId namespace.sqlId
	 *
	 * @param sqlId
	 * @return
	 */
	protected final String sqlId(String sqlId) {
		return sqlId.contains(".") ? sqlId
				: (entityClass.getSimpleName() + "." + sqlId);
	}

	/**
	 * 获得 默认的 pagination 分页,注意 分页 listSqlId ： queryForPaginate，分页 countSqlId ：
	 * count
	 *
	 * @param pagination
	 *            带返回类的查询条件
	 * @return Pagination
	 */
	public Pagination<T> getPagination(Pagination<T> pagination) {
		return getPagination(sqlId(DAO_QUERY_FOR_PAGINATE), sqlId(DAO_COUNT),
				pagination);
	}

	/**
	 * 获得 pagination 分页，不包含返回 count
	 *
	 * @param listSql
	 *            列表页 sqlId
	 * @param pagination
	 *            带返回类的查询条件
	 * @return Pagination
	 */
	public Pagination<T> getPaginationWithOutCount(String listSql,
			Pagination<T> pagination) {
		return getPagination(listSql, StringUtils.EMPTY, pagination);
	}

	/**
	 * 获得 pagination 分页，指定listSql,countSql
	 *
	 * @param listSql
	 *            用于查询的列表sql，带命名空间
	 * @param countSql
	 *            用于统计的sql，带命名空间
	 * @param pagination
	 *            带返回类的查询条件
	 * @return Pagination
	 */
	public Pagination<T> getPagination(String listSql, String countSql,
			Pagination<T> pagination) {
		List<T> temp = this.queryDao.getSqlSession().selectList(listSql,
				pagination);
		pagination.setList(temp);
		if (StringUtils.isNotBlank(countSql)) {
			int totalRecords = this.queryDao.getSqlSession().selectOne(
					countSql, pagination);
			pagination.setTotalRecords(totalRecords);
		}
		return pagination;
	}

	/**
	 * 获得指定记录，可动态指定返回子类
	 *
	 * @param sqlId
	 *            xml的id ,如果id包含命名空间则直接用id，否则增加 T 为 命名空间,比如 A.B 则不变，不然会调整为 A.B
	 * @param obj
	 *            查询参数(对象)
	 * @param sonClass
	 *            返回子类
	 * @return T
	 */
	public T selectOne(String sqlId, Object obj, Class<? extends T> sonClass) {
		return null;//this.dymanicSelectOne(sqlId, obj, sonClass);
	}

	public synchronized T aaa(String sqlId, Class<? extends T> c) {
		MappedStatement statement = this.queryDao.getSqlSession()
				.getConfiguration().getMappedStatement(sqlId(sqlId));
		ResultMap map = statement.getResultMaps().get(0);
		T t = null;
		if (!map.getClass().equals(c)) {
			Field field = FieldUtils.getDeclaredField(map.getClass(), "type",
					true);
			try {
				Object c2 = field.get(map);
				field.set(map, c);
				t = this.queryDao.getSqlSession()
						.selectOne(sqlId(sqlId), 86455);
				field.set(map, c2);
			} catch (IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return t;
	}

	public synchronized List<T> bbb(String sqlId, Class<? extends T> c) {
		MappedStatement statement = this.queryDao.getSqlSession()
				.getConfiguration().getMappedStatement(sqlId(sqlId));
		ResultMap map = statement.getResultMaps().get(0);
		List<T> list = null;
		if (!map.getClass().equals(c)) {
			Field field = FieldUtils.getDeclaredField(map.getClass(), "type",
					true);
			try {
				Object c2 = field.get(map);
				field.set(map, c);
				list = this.queryDao.getSqlSession().selectList(sqlId(sqlId),
						86455);
				// 先不修改回去，继续查询子类
				field.set(map, c2);
			} catch (IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	/*private T dymanicTypeSelectOne(String sqlId, Object obj,
			Class<? extends T> sonClass) {
		String key=forStatementKey(sqlId, sonClass);
		Configuration configuration=this.queryDao.getSqlSession()
				.getConfiguration();
		MappedStatement foundStmt=configuration.getMappedStatement(key);
		if (foundStmt ==null) {
			resolveNewTypeStatement(sqlId, sonClass,key);
		}
		return queryDao
	}*/
	private String forStatementKey(String sqlId,Class<?> cls){
		if (sqlId==null || cls==null) {
			throw new IllegalArgumentException("sqlId或者resultType不能为null");
		}
		return sqlId+"."+cls.getCanonicalName();
	}
	/**
	 * 只支持返回结果为resultType的类型
	 * 如果查询涉及缓存，则结果不可知
	 * 不支持一次查询返回多个ResultSet
	 * @param sqlId
	 * @param clsType
	 */
	private void resolveNewTypeStatement(String sqlId,Class<?> clsType,String typeKey){
		//currentStatmentId
		Configuration configuration=this.queryDao.getSqlSession()
				.getConfiguration();
		MappedStatement stmt =configuration.getMappedStatement(sqlId(sqlId));
		if (stmt == null) {
			throw new IllegalArgumentException("sqlId="+sqlId+"的语句没有找到。");
		}
		MappedStatement.Builder statementBuilder = new MappedStatement.Builder(stmt.getConfiguration(),sqlId,
				stmt.getSqlSource(), stmt.getSqlCommandType());
	    statementBuilder.resource(stmt.getResource());
	    statementBuilder.fetchSize(stmt.getFetchSize());
	    statementBuilder.statementType(stmt.getStatementType());
	    //不支持selectKey等insert操作
	    statementBuilder.keyGenerator(null);
	    statementBuilder.keyProperty(null);
	    statementBuilder.keyColumn(null);
	    statementBuilder.databaseId(stmt.getDatabaseId());
	    statementBuilder.lang(stmt.getLang());
	    statementBuilder.resultOrdered(stmt.isResultOrdered());
	    //不支持一次查询返回多个结果集
	    statementBuilder.resulSets(null);
	    statementBuilder.timeout(stmt.getTimeout());
        statementBuilder.parameterMap(stmt.getParameterMap());
        //不支持使用resultMap,只可以使用resultType
        List<ResultMap> resultMaps=new ArrayList<ResultMap>();
        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                configuration,
                statementBuilder.id() + "-Inline",
                clsType,
                new ArrayList<ResultMapping>(),
                null);
        resultMaps.add(inlineResultMapBuilder.build());
        statementBuilder.resultMaps(resultMaps);
        statementBuilder.flushCacheRequired(stmt.isFlushCacheRequired());
        statementBuilder.useCache(stmt.isUseCache());
        statementBuilder.cache(stmt.getCache());
        configuration.addMappedStatement(statementBuilder.build());
	}
	/**
	 * 获得超类
	 * @param clazz
	 * @param index
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static Class getSuperClassGenricType(Class clazz, int index) {
		Type genType = clazz.getGenericSuperclass();

		if (!(genType instanceof ParameterizedType)) {
			return Object.class;
		}

		Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

		if (index >= params.length || index < 0) {
			return Object.class;
		}
		if (!(params[index] instanceof Class)) {
			return Object.class;
		}
		return (Class) params[index];
	}
}
